﻿using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using mimosa.Net;
using mimosa.SIP.Transactions;
using mimosa.SIP.Dialogue;
using mimosa.Utilities;
using log4net;

namespace mimosa.SIP
{
    public delegate void SIPTransportRequestReceivedDelegate(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPRequest sipRequest);
    public delegate void SIPTransportResponseReceivedDelegate(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPResponse sipResponse);

    public delegate void SIPTransportSIPRequestInTraceDelegate(IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPRequest sipRequest);
    public delegate void SIPTransportSIPRequestOutTraceDelegate(IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPRequest sipRequest);
    public delegate void SIPTransportSIPResponseInTraceDelegate(IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPResponse sipResponse);
    public delegate void SIPTransportSIPResponseOutTraceDelegate(IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPResponse sipResponse);

    public delegate void STUNRequestReceivedDelegate(IPEndPoint receivedEndPoint, IPEndPoint remoteEndPoint, byte[] buffer, int bufferLength);
    public delegate void UnrecognisedMessageReceivedDelegate(IPEndPoint localEndPoint, IPEndPoint fromEndPoint, byte[] buffer);

    public class SIPTransport
    {
        private const int MAX_QUEUEWAIT_PERIOD = 2000;              // Maximum time to wait to check the message received queue if no events are received.
        private const int PENDINGREQUESTS_CHECK_PERIOD = 500;       // Time between checking the pending requests queue to resend reliable requests that have not been responded to.
        private const int TIME_WAIT_FINALRESPONSE = 2000;           // Milliseconds to wait after transmitting the final request on a reliable transmission before timing out the request.
        private const string THREAD_NAME = "sip-transport";
        private const int MAX_INMESSAGE_QUEUECOUNT = 5000;          // The maximum number of messages that can be stored in the incoming message queue.
        private const int MAX_RELIABLETRANSMISSIONS_COUNT = 5000;   // The maximum number of messages that can be maintained for reliable transmissions.

        protected static readonly int m_transactionDurationMultiplier = SIPTransaction.TRANSACTION_DURATION;    // this x T1 is how long a reliable request will have retransmits performed for.
        protected static readonly int m_t1 = SIPTimings.T1;       // SIP Timer T1 in milliseconds.

        private static ILog logger = AppState.logger;

        private static bool m_queueIncoming = true;     // Dictates whether the transport later will queue incoming requests for processing on a separate thread of process immediately on the same thread.
        // Most SIP elements with the exception of Stateless Proxies would typically want to queue incoming SIP messages.

        private static bool m_transportThreadStarted = false;
        private static Queue m_inMessageQueue = new Queue();
        private static ManualResetEvent m_inMessageArrived = new ManualResetEvent(false);
        private static bool m_closed = false;
        private static Thread m_inMessageThread = null;

        //private SIPChannel m_sipChannel;

        public static TimeSpan TimeTakenProcessLastMessage;

        private static List<string> m_localSIPSockets = new List<string>();
        private static List<SIPChannel> m_sipChannels = new List<SIPChannel>();

        public event SIPTransportRequestReceivedDelegate SIPTransportRequestReceived;
        public event SIPTransportResponseReceivedDelegate SIPTransportResponseReceived;

        public static event SIPTransportSIPRequestInTraceDelegate SIPRequestInTraceEvent;
        public static event SIPTransportSIPRequestOutTraceDelegate SIPRequestOutTraceEvent;
        public static event SIPTransportSIPResponseInTraceDelegate SIPResponseInTraceEvent;
        public static event SIPTransportSIPResponseOutTraceDelegate SIPResponseOutTraceEvent;
        public static event STUNRequestReceivedDelegate STUNRequestReceived;
        public static event UnrecognisedMessageReceivedDelegate UnrecognisedMessageReceived;

        // Contains a list of the SIP Requests/Response that are being monitored or responses and retransmitted on when none is recieved to attempt a more reliable delivery
        // rather then just relying on the initial request to get through.
        private static Dictionary<string, SIPTransaction> m_reliableTransmissions = new Dictionary<string, SIPTransaction>();
        private static bool m_reliablesThreadRunning = false;   // Only gets started when a request is made to send a reliable request.

        public IPEndPoint SIPChannelEndPoint
        {
            get { return m_sipChannels[0].SIPChannelEndPoint; }
        }

        public SIPTransport()
        { }

        public SIPTransport(bool queueIncoming)
        {
            m_queueIncoming = queueIncoming;
        }

        public SIPTransport(IPEndPoint localEndPoint)
        {
            AddSIPChannel(localEndPoint);
            StartTransportThread();
        }

        public SIPTransport(IPEndPoint localEndPoint, bool queueIncoming)
        {
            AddSIPChannel(localEndPoint);

            m_queueIncoming = queueIncoming;

            if (m_queueIncoming)
            {
                StartTransportThread();
            }
        }

        /// <summary>
        /// Adds an additional SIP Channel to the transport layer.
        /// </summary>
        /// <param name="localEndPoint"></param>
        public void AddSIPChannel(IPEndPoint localEndPoint)
        {
            try
            {
                SIPChannel sipChannel = new SIPChannel(localEndPoint);

                bool channelInList = false;
                foreach (SIPChannel existingSIPChannel in m_sipChannels)
                {
                    if (existingSIPChannel.SIPChannelEndPoint.Address.ToString() == localEndPoint.Address.ToString() &&
                        existingSIPChannel.SIPChannelEndPoint.Port == localEndPoint.Port)
                    {
                        channelInList = true;
                        break;
                    }
                }

                if (!channelInList)
                {
                    m_localSIPSockets.Add(localEndPoint.ToString());
                    m_sipChannels.Add(sipChannel);
                }

                // Wire up the SIP transport to the SIP channel.
                sipChannel.SIPMessageReceived += new SIPMessageReceivedDelegate(ReceiveMessage);

                if (m_queueIncoming && !m_transportThreadStarted)
                {
                    StartTransportThread();
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception AddSIPChannel. " + excp.Message);
                throw excp;
            }
        }

        private void StartTransportThread()
        {
            if (!m_transportThreadStarted)
            {
                m_transportThreadStarted = true;

                Thread inMessageThread = new Thread(new ThreadStart(ProcessInMessage));
                inMessageThread.Name = THREAD_NAME;
                inMessageThread.Start();
            }
        }

        private static void StartReliableTransmissionsThread()
        {
            m_reliablesThreadRunning = true;

            Thread reliableTransmissionsThread = new Thread(new ThreadStart(ProcessPendingReliableRequests));
            reliableTransmissionsThread.Name = "siptransport-reliable";
            reliableTransmissionsThread.Start();
        }

        public void ReceiveMessage(SIPChannel sipChannel, IPEndPoint remoteEndPoint, byte[] buffer)
        {
            try
            {
                if (!m_queueIncoming)
                {
                    DateTime startTime = DateTime.Now;

                    SIPMessageReceived(sipChannel, remoteEndPoint, buffer);

                    TimeTakenProcessLastMessage = DateTime.Now.Subtract(startTime);
                }
                else
                {
                    IncomingMessage incomingMessage = new IncomingMessage(sipChannel, remoteEndPoint, buffer);

                    lock (m_inMessageQueue.SyncRoot)
                    {
                        // Keep the queue within size limits 
                        if (m_inMessageQueue.Count >= MAX_INMESSAGE_QUEUECOUNT)
                        {
                            logger.Warn("The SIPTransport incoming message queue size of " + m_inMessageQueue.Count + " exceeded the allowed limit of " +
                                MAX_INMESSAGE_QUEUECOUNT + " and forced the oldest messages in the queue to be dropped.");

                            while (m_inMessageQueue.Count >= MAX_INMESSAGE_QUEUECOUNT)
                            {
                                m_inMessageQueue.Dequeue();
                            }
                        }

                        m_inMessageQueue.Enqueue(incomingMessage);
                        m_inMessageArrived.Set();
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport ReceiveMessage. " + excp.Message);
                throw excp;
            }
        }

        public static void Shutdown()
        {
            try
            {
                m_closed = true;

                foreach (SIPChannel channel in m_sipChannels)
                {
                    channel.Close();
                }

                m_inMessageArrived.Set();
                m_inMessageArrived.Set();

                if (m_inMessageThread != null)
                {
                    logger.Debug("Shutting down SIPTransport thread " + m_inMessageThread.Name);
                    m_inMessageThread.Join();
                    logger.Debug("SIPTransport thread shutdown.");
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport Shutdown. " + excp.Message);
            }
        }

        public static IPEndPoint GetTransportContact(IPEndPoint dstEndPoint)
        {
            return m_sipChannels[0].SIPChannelEndPoint;
        }

        public static IPEndPoint[] GetIPEndPointsList()
        {
            IPEndPoint[] endPointsList = new IPEndPoint[] { m_sipChannels[0].SIPChannelEndPoint };

            return endPointsList;
        }

        public static void Send(IPEndPoint destinationEndPoint, byte[] buffer)
        {
            m_sipChannels[0].Send(destinationEndPoint, buffer);
        }

        public static void SendFrom(IPEndPoint localEndPoint, IPEndPoint destinationEndPoint, byte[] buffer)
        {
            SIPChannel sendSIPChannel = FindSIPChannelForSocket(localEndPoint);
            if (sendSIPChannel == null)
            {
                sendSIPChannel = m_sipChannels[0];
            }

            sendSIPChannel.Send(destinationEndPoint, buffer);
        }

        public static void SendRequest(SIPRequest sipRequest)
        {
            SendRequest(sipRequest.GetRequestEndPoint(), sipRequest);
        }

        public static void SendRequest(IPEndPoint destinationEndPoint, SIPRequest sipRequest)
        {
            if (SIPRequestOutTraceEvent != null)
            {
                FireSIPRequestOutTraceEvent(m_sipChannels[0].SIPChannelEndPoint, destinationEndPoint, sipRequest);
            }

            m_sipChannels[0].Send(destinationEndPoint, sipRequest.ToString());
        }

        public static void SendRequestFrom(IPEndPoint localEndPoint, IPEndPoint dstEndPoint, SIPRequest sipRequest)
        {
            SIPChannel sendSIPChannel = FindSIPChannelForSocket(localEndPoint);
            if (sendSIPChannel == null)
            {
                sendSIPChannel = m_sipChannels[0];
            }

            sendSIPChannel.Send(dstEndPoint, sipRequest.ToString());

            if (SIPRequestOutTraceEvent != null)
            {
                FireSIPRequestOutTraceEvent(sendSIPChannel.SIPChannelEndPoint, dstEndPoint, sipRequest);
            }
        }

        /// <summary>
        /// Sends a SIP request/response and keeps track of whether a response/acknowledgement has been received. If no response is received then periodic retransmits are made
        /// for up to T1 x 64 seconds.
        /// </summary>
        public static void SendSIPReliable(SIPTransaction sipTransaction)
        {
            if (m_reliableTransmissions.Count >= MAX_RELIABLETRANSMISSIONS_COUNT)
            {
                throw new ApplicationException("Cannot send reliable SIP message as the reliable transmissions queue is full.");
            }

            if (sipTransaction.TransactionType == SIPTransactionTypesEnum.Invite &&
                sipTransaction.TransactionState == SIPTransactionStatesEnum.Completed)
            {
                // This is an INVITE transaction that wants to send a reliable response.
                SendResponse(sipTransaction.FinalResponse);
            }
            else
            {
                if (sipTransaction.RemoteEndPoint == null)
                {
                    if (sipTransaction.SourceEndPoint != null)
                    {
                        SendRequestFrom(sipTransaction.SourceEndPoint, sipTransaction.InitialRequest.GetRequestEndPoint(), sipTransaction.InitialRequest);
                    }
                    else
                    {
                        SendRequest(sipTransaction.InitialRequest);
                    }
                }
                else
                {
                    if (sipTransaction.SourceEndPoint != null)
                    {
                        SendRequestFrom(sipTransaction.SourceEndPoint, sipTransaction.RemoteEndPoint, sipTransaction.InitialRequest);
                    }
                    else
                    {
                        SendRequest(sipTransaction.RemoteEndPoint, sipTransaction.InitialRequest);
                    }
                }
            }

            sipTransaction.Retransmits = 1;
            sipTransaction.InitialTransmit = DateTime.Now;
            sipTransaction.LastTransmit = DateTime.Now;
            sipTransaction.DeliveryPending = true;

            if (!m_reliableTransmissions.ContainsKey(sipTransaction.TransactionId))
            {
                lock (m_reliableTransmissions)
                {
                    m_reliableTransmissions.Add(sipTransaction.TransactionId, sipTransaction);
                }
            }

            if (!m_reliablesThreadRunning)
            {
                StartReliableTransmissionsThread();
            }
        }

        public static void SendResponse(SIPResponse sipResponse)
        {
            SendResponse(sipResponse.Header.Via.TopViaHeader.GetEndPoint(), sipResponse);
        }

        public static void SendResponse(IPEndPoint destinationEndPoint, SIPResponse sipResponse)
        {
            if (SIPResponseOutTraceEvent != null)
            {
                FireSIPResponseOutTraceEvent(m_sipChannels[0].SIPChannelEndPoint, destinationEndPoint, sipResponse);
            }

            m_sipChannels[0].Send(destinationEndPoint, sipResponse.ToString());
        }

        public static void SendResponseFrom(IPEndPoint localEndPoint, IPEndPoint dstEndPoint, SIPResponse sipResponse)
        {
            SIPChannel sendSIPChannel = FindSIPChannelForSocket(localEndPoint);
            if (sendSIPChannel == null)
            {
                sendSIPChannel = m_sipChannels[0];
            }

            sendSIPChannel.Send(dstEndPoint, sipResponse.ToString());

            if (SIPRequestOutTraceEvent != null)
            {
                FireSIPResponseOutTraceEvent(sendSIPChannel.SIPChannelEndPoint, dstEndPoint, sipResponse);
            }
        }

        private void ProcessInMessage()
        {
            try
            {
                while (!m_closed)
                {
                    while (m_inMessageQueue.Count > 0)
                    {
                        IncomingMessage incomingMessage = null;

                        lock (m_inMessageQueue.SyncRoot)
                        {
                            incomingMessage = (IncomingMessage)m_inMessageQueue.Dequeue();
                        }

                        if (incomingMessage != null)
                        {
                            try
                            {
                                SIPMessageReceived(incomingMessage.InSIPChannel, incomingMessage.InEndPoint, incomingMessage.Buffer);
                            }
                            catch (Exception recvdExcp)
                            {
                                logger.Error("SIPTransport ProcessInMessage. " + recvdExcp.Message);
                            }
                        }
                    }

                    m_inMessageArrived.Reset();
                    m_inMessageArrived.WaitOne(MAX_QUEUEWAIT_PERIOD, false);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport ProcessInMessage. " + excp.Message);
            }
        }

        private static void ProcessPendingReliableRequests()
        {
            try
            {
                m_reliablesThreadRunning = true;

                while (!m_closed)
                {
                    if (m_reliableTransmissions.Count == 0)
                    {
                        // No request retransmissions in progress close down thread until next one required.
                        m_reliablesThreadRunning = false;
                        break;
                    }

                    try
                    {
                        List<string> timedOutTransactions = new List<string>();

                        lock (m_reliableTransmissions)
                        {
                            foreach (SIPTransaction transaction in m_reliableTransmissions.Values)
                            {
                                if (transaction.DeliveryPending && transaction.TransactionState != SIPTransactionStatesEnum.Terminated)
                                {
                                    if (DateTime.Now.Subtract(transaction.InitialTransmit).TotalMilliseconds > (m_t1 * m_transactionDurationMultiplier * 2 + TIME_WAIT_FINALRESPONSE))
                                    {
                                        //logger.Debug("Request timed out " + transaction.InitialRequest.Method + " " + transaction.InitialRequest.URI.ToString() + ".");

                                        transaction.DeliveryFailed = true;
                                        transaction.TimedOut();
                                        timedOutTransactions.Add(transaction.TransactionId);
                                    }
                                    else
                                    {
                                        double nextTransmitMilliseconds = Math.Pow(2, transaction.Retransmits - 1) * m_t1;
                                        //logger.Debug("Time since retransmit " + transaction .RequestTransmits + " for " + transaction.InitialRequest.Method + " " + transaction.InitialRequest.URI.ToString() + " " + DateTime.Now.Subtract(transaction.LastRequestTransmit).TotalMilliseconds + ".");

                                        if (DateTime.Now.Subtract(transaction.LastTransmit).TotalMilliseconds >= nextTransmitMilliseconds)
                                        {
                                            //logger.Debug("Retransmit " + transaction.RequestTransmits + " for request " + transaction.InitialRequest.Method + " " + transaction.InitialRequest.URI.ToString() + ", last=" + DateTime.Now.Subtract(transaction.LastRequestTransmit).TotalMilliseconds + "ms, first=" + DateTime.Now.Subtract(transaction.InitialRequestTransmit).TotalMilliseconds  + "ms.");
                                            transaction.Retransmits = transaction.Retransmits + 1;
                                            transaction.LastTransmit = DateTime.Now;

                                            if (transaction.TransactionType == SIPTransactionTypesEnum.Invite &&
                                                transaction.TransactionState == SIPTransactionStatesEnum.Completed)
                                            {
                                                // This is an INVITE transaction that wants to send a reliable response.
                                                SendResponse(transaction.FinalResponse);
                                                transaction.ResponseRetransmit();
                                            }
                                            else
                                            {
                                                if (transaction.SourceEndPoint != null)
                                                {
                                                    SendRequestFrom(transaction.SourceEndPoint, transaction.InitialRequest.GetRequestEndPoint(), transaction.InitialRequest);
                                                }
                                                else
                                                {
                                                    SendRequest(transaction.InitialRequest);
                                                }

                                                transaction.RequestRetransmit();
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    timedOutTransactions.Add(transaction.TransactionId);
                                }
                            }

                            // Remove timed out or complete transactions from reliable transmissions list.
                            if (timedOutTransactions.Count > 0)
                            {
                                foreach (string transactionId in timedOutTransactions)
                                {
                                    if (m_reliableTransmissions.ContainsKey(transactionId))
                                    {
                                        m_reliableTransmissions.Remove(transactionId);
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception excp)
                    {
                        logger.Error("Exception SIPTransport ProcessPendingRequests checking pendings. " + excp.Message);
                    }

                    Thread.Sleep(PENDINGREQUESTS_CHECK_PERIOD);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport ProcessPendingRequests. " + excp.Message);
            }
            finally
            {
                m_reliablesThreadRunning = false;
            }
        }

        private void SIPMessageReceived(SIPChannel sipChannel, IPEndPoint inEndPoint, byte[] buffer)
        {
            try
            {
                if (buffer != null && buffer.Length > 0)
                {
                    if ((buffer[0] == 0x0 || buffer[0] == 0x1) && buffer.Length >= 20)
                    {
                        // Treat any messages that cannot be SIP as STUN requests.
                        if (STUNRequestReceived != null)
                        {
                            STUNRequestReceived(sipChannel.SIPChannelEndPoint, inEndPoint, buffer, buffer.Length);
                        }
                    }
                    else
                    {
                        // Treat all messages that don't match STUN requests as SIP.
                        if (buffer.Length > SIPConstants.SIP_MAXIMUM_LENGTH)
                        {
                            logger.Warn("SIPTransport dropping Excessive Length SIP Message " + buffer.Length + " bytes from " + IPSocket.GetSocketString(inEndPoint) + ".");
                            SIPResponse tooLargeResponse = GetResponse(sipChannel.SIPChannelEndPoint, inEndPoint, SIPResponseStatusCodesEnum.MessageTooLarge, null);
                            SendResponse(tooLargeResponse);
                            return;
                        }

                        SIPMessage sipMessage = SIPMessage.ParseSIPMessage(buffer, inEndPoint);

                        if (sipMessage != null)
                        {
                            if (sipMessage.SIPMessageType == SIPMessageTypesEnum.Response)
                            {
                                #region SIP Response.

                                SIPResponseParserError responseParserError = SIPResponseParserError.None;
                                SIPResponse sipResponse = SIPResponse.ParseSIPResponse(sipMessage, out responseParserError);
                                sipResponse.ReceivedFrom = inEndPoint;

                                if (sipResponse != null && responseParserError == SIPResponseParserError.None)
                                {
                                    if (sipResponse.Valid)
                                    {
                                        if (SIPResponseInTraceEvent != null)
                                        {
                                            FireSIPResponseInTraceEvent(sipChannel.SIPChannelEndPoint, inEndPoint, sipResponse);
                                        }

                                        if (SIPTransaction.Exists(sipResponse))
                                        {
                                            SIPTransaction transaction = SIPTransaction.GetTransaction(sipResponse);
                                            transaction.DeliveryPending = false;

                                            if (m_reliableTransmissions.ContainsKey(transaction.TransactionId))
                                            {
                                                lock (m_reliableTransmissions)
                                                {
                                                    m_reliableTransmissions.Remove(transaction.TransactionId);
                                                }
                                            }

                                            transaction.GotResponse(sipChannel.SIPChannelEndPoint, inEndPoint, sipResponse);
                                        }
                                        else if (SIPTransportResponseReceived != null)
                                        {
                                            SIPTransportResponseReceived(sipChannel.SIPChannelEndPoint, inEndPoint, sipResponse);
                                        }
                                    }
                                    else
                                    {
                                        logger.Error("Invalid SIP response " + sipResponse.ValidationError + " , ignoring.");
                                        logger.Error(sipMessage.RawMessage);
                                    }
                                }
                                else
                                {
                                    logger.Error("Invalid SIP response " + responseParserError + ", ignoring.");
                                    logger.Error(sipMessage.RawMessage);
                                }

                                #endregion
                            }
                            else
                            {
                                #region SIP Request.

                                SIPRequestParserError requestParserError = SIPRequestParserError.None;
                                SIPRequest sipRequest = SIPRequest.ParseSIPRequest(sipMessage, out requestParserError);
                                sipRequest.ReceivedFrom = inEndPoint;

                                if (SIPRequestInTraceEvent != null)
                                {
                                    FireSIPRequestInTraceEvent(sipChannel.SIPChannelEndPoint, inEndPoint, sipRequest);
                                }

                                if (sipRequest != null && requestParserError == SIPRequestParserError.None)
                                {
                                    if (sipRequest.Valid)
                                    {
                                        // Stateful cores will create transactions once they get the request and the transport layer will use those transactions.
                                        // Stateless cores will not be affected by this step as the transaction layer will always return false.
                                        if (SIPTransaction.Exists(sipRequest))
                                        {
                                            if (SIPTransaction.IsCompletedTransaction(sipRequest))
                                            {
                                                logger.Debug("Resending final response for " + sipRequest.Method + " " + sipRequest.URI.ToString() + " from " + IPSocket.GetSocketString(inEndPoint) + ".");
                                                SIPTransaction completedTransaction = SIPTransaction.GetTransaction(sipRequest);

                                                SIPResponse finalResponse = completedTransaction.FinalResponse;
                                                SendResponse(finalResponse.Header.Via.TopViaHeader.GetEndPoint(), finalResponse);
                                            }
                                            else
                                            {
                                                if (sipRequest.Method == SIPMethodsEnum.ACK)
                                                {
                                                    SIPTransaction inviteTransaction = SIPTransaction.GetTransaction(sipRequest);
                                                    inviteTransaction.DeliveryPending = false;
                                                    inviteTransaction.TransactionState = SIPTransactionStatesEnum.Confirmed;

                                                    logger.Debug("ACK received for INVITE, setting state to Confirmed, " + sipRequest.URI.ToString() + " from " + sipRequest.Header.From.FromURI.User + " " + IPSocket.GetSocketString(inEndPoint) + ".");
                                                }
                                                else
                                                {
                                                    logger.Debug("Transaction already exists, ignoring duplicate request, " + sipRequest.Method + " " + sipRequest.URI.ToString() + " from " + sipRequest.Header.From.FromURI.User + " " + IPSocket.GetSocketString(inEndPoint) + ".");
                                                }
                                            }
                                        }
                                        else if (sipRequest.Method == SIPMethodsEnum.BYE && SIPDialogue.DialogueExists(sipRequest.Header.CallId))
                                        {
                                            // Dialogue found, hangup call.
                                            SIPDialogue.RemoveDialogue(sipRequest.Header.CallId);
                                            SIPResponse byeResponse = GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.Ok, null);
                                            SIPTransport.SendResponse(byeResponse);
                                        }
                                        else if (SIPTransportRequestReceived != null)
                                        {
                                            // This is a new SIP request and if the validity checks are passed it will be handed off to all subscribed new request listeners.

                                            #region Check for invalid SIP requests.

                                            if (sipRequest.Header.MaxForwards == 0 && sipRequest.Method != SIPMethodsEnum.OPTIONS)
                                            {
                                                // Check the MaxForwards value, if equal to 0 the request must be discarded. If MaxForwards is -1 it indicates the
                                                // header was not present in the request and that the MaxForwards check should not be undertaken.
                                                logger.Warn("SIPTransport responding with TooManyHops due to 0 MaxForwards.");
                                                SIPResponse tooManyHops = GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.TooManyHops, null);
                                                SIPTransport.SendResponse(inEndPoint, tooManyHops);
                                                return;
                                            }
                                            else if (sipRequest.IsLoop(sipChannel.SIPChannelEndPoint.Address.ToString(), sipChannel.SIPChannelEndPoint.Port, sipRequest.CreateBranchId()))
                                            {
                                                logger.Warn("SIPTransport Dropping looped request.");
                                                SIPResponse loopResponse = GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.LoopDetected, null);
                                                SendResponse(loopResponse);
                                                return;
                                            }

                                            #endregion

                                            #region Route pre-processing.

                                            if (sipRequest.Header.Routes.Length > 0)
                                            {
                                                sipRequest.PreProcessRouteInfo(m_localSIPSockets);
                                            }

                                            #endregion

                                            // Request has passed validity checks, adjust the client Via header to reflect the socket the request was received on.
                                            //SIPViaHeader originalTopViaHeader = sipRequest.Header.Via.TopViaHeader;
                                            sipRequest.Header.Via.UpateTopViaHeader(inEndPoint);

                                            // Stateful cores should create a transaction once they receive this event, stateless cores should not.
                                            SIPTransportRequestReceived(sipChannel.SIPChannelEndPoint, inEndPoint, sipRequest);
                                        }
                                    }
                                    else
                                    {
                                        logger.Error("Invalid SIP request, validation error, " + sipRequest.ValidationError.ToString() + ", sending BadRequest error response to " + inEndPoint.ToString() + ".");

                                        if (sipRequest != null)
                                        {
                                            SIPResponse errorResponse = GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.BadRequest, sipRequest.ValidationError.ToString());
                                            SendResponse(inEndPoint, errorResponse);
                                        }

                                        //logger.Error(sipMessage.RawMessage);
                                    }
                                }
                                else
                                {
                                    logger.Error("Invalid SIP request, parser error, " + requestParserError + ", sending BadRequest error response to " + inEndPoint.ToString() + ".");

                                    if (sipRequest != null)
                                    {
                                        SIPResponse errorResponse = GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.BadRequest, requestParserError.ToString());
                                        SendResponse(inEndPoint, errorResponse);
                                    }

                                    //logger.Error(sipMessage.RawMessage);
                                }

                                #endregion
                            }
                        }
                        else
                        {
                            if (UnrecognisedMessageReceived != null)
                            {
                                UnrecognisedMessageReceived(sipChannel.SIPChannelEndPoint, inEndPoint, buffer);
                            }
                        }
                    }
                }
                else
                {
                    if (UnrecognisedMessageReceived != null)
                    {
                        UnrecognisedMessageReceived(sipChannel.SIPChannelEndPoint, inEndPoint, buffer);
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport SIPMessageReceived. " + excp.Message);
                //throw excp;
            }
        }

        /// <summary>
        /// Checks the Contact SIP URI host and if it is recognised as a private address it is replaced with the socket
        /// the SIP message was received on.
        /// 
        /// Private address space blocks RFC 1597.
        ///		10.0.0.0        -   10.255.255.255
        ///		172.16.0.0      -   172.31.255.255
        ///		192.168.0.0     -   192.168.255.255
        ///
        /// </summary>
        public static bool IsPrivateAddress(string host)
        {
            if (host != null && host.Trim().Length > 0)
            {
                if (host.StartsWith("127.0.0.1") ||
                    host.StartsWith("10.") ||
                    Regex.Match(host, @"^172\.2\d\.").Success ||
                    host.StartsWith("172.30.") ||
                    host.StartsWith("172.31.") ||
                    host.StartsWith("169.254.") ||
                    host.StartsWith("192.168."))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                logger.Error("Cannot check private address against an empty host.");
                return false;
            }
        }

        public static SIPResponse GetResponse(SIPHeader requestHeader, SIPResponseStatusCodesEnum responseCode, string reasonPhrase)
        {
            try
            {
                SIPResponse response = new SIPResponse(responseCode);

                if (reasonPhrase != null)
                {
                    response.ReasonPhrase = reasonPhrase;
                }

                response.Header = new SIPHeader(requestHeader.From, requestHeader.To, requestHeader.CSeq, requestHeader.CallId);
                response.Header.CSeqMethod = requestHeader.CSeqMethod;
                response.Header.Via = requestHeader.Via;
                response.Header.MaxForwards = Int32.MinValue;

                return response;
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport GetResponse. " + excp.Message);
                throw excp;
            }
        }

        /// <summary>
        /// Checks the list of local SIP sockets to determine if the specified one is in the list.
        /// </summary>
        public static bool IsLocalSIPSocket(string socket)
        {
            return m_localSIPSockets.Contains(socket);
        }

        /// <summary>
        ///  This method is used to generate a response in cases where the incoming message does not get parsed, such as if the messageis too big. 
        ///  In those cases there is nothing to go off except the socket the request was received on.
        /// </summary>
        private static SIPResponse GetResponse(IPEndPoint localEndPoint, IPEndPoint requestEndPoint, SIPResponseStatusCodesEnum responseCode, string reasonPhrase)
        {
            try
            {
                SIPResponse response = new SIPResponse(responseCode);

                if (reasonPhrase != null)
                {
                    response.ReasonPhrase = reasonPhrase;
                }

                SIPToHeader toHeader = new SIPToHeader(null, SIPURI.ParseSIPURI("sip:" + IPSocket.GetSocketString(localEndPoint)), CallProperties.CreateNewTag());
                SIPFromHeader fromHeader = new SIPFromHeader(null, SIPURI.ParseSIPURI("sip:" + IPSocket.GetSocketString(requestEndPoint)), null);

                response.Header = new SIPHeader(fromHeader, toHeader, 1, CallProperties.CreateNewCallId());
                response.Header.MaxForwards = Int32.MinValue;

                response.Header.Via = new SIPViaSet();
                response.Header.Via.PushViaHeader(new SIPViaHeader(localEndPoint.Address.ToString(), localEndPoint.Port, CallProperties.CreateBranchId()));

                return response;
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPTransport GetResponse. " + excp.Message);
                throw excp;
            }
        }

        private static SIPChannel FindSIPChannelForSocket(IPEndPoint localEndPoint)
        {
            SIPChannel sendSIPChannel = null;

            // Attempt to find the SIP Channel that has an end point of srcEndPoint.
            foreach (SIPChannel sipChannel in m_sipChannels)
            {
                if (sipChannel.SIPChannelEndPoint.Address.ToString() == localEndPoint.Address.ToString() &&
                    sipChannel.SIPChannelEndPoint.Port == localEndPoint.Port)
                {
                    sendSIPChannel = sipChannel;
                }
            }

            return sendSIPChannel;
        }

        private static void FireSIPRequestInTraceEvent(IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPRequest sipRequest)
        {
            try
            {
                if (SIPRequestInTraceEvent != null)
                {
                    SIPRequestInTraceEvent(localEndPoint, fromEndPoint, sipRequest);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPRequestInTraceEvent. " + excp.Message);
            }
        }

        private static void FireSIPRequestOutTraceEvent(IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPRequest sipRequest)
        {
            try
            {
                if (SIPRequestOutTraceEvent != null)
                {
                    SIPRequestOutTraceEvent(localEndPoint, toEndPoint, sipRequest);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPRequestOutTraceEvent. " + excp.Message);
            }
        }

        private static void FireSIPResponseInTraceEvent(IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPResponse sipResponse)
        {
            try
            {
                if (SIPResponseInTraceEvent != null)
                {
                    SIPResponseInTraceEvent(localEndPoint, fromEndPoint, sipResponse);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPResponseInTraceEvent. " + excp.Message);
            }
        }

        private static void FireSIPResponseOutTraceEvent(IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPResponse sipResponse)
        {
            try
            {
                if (SIPResponseOutTraceEvent != null)
                {
                    SIPResponseOutTraceEvent(localEndPoint, toEndPoint, sipResponse);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception FireSIPResponseOutTraceEvent. " + excp.Message);
            }
        }
    }
}
